import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static sun.misc.PostVMInitHook.run;

public class TcpEchoServer {
    ServerSocket serverSocket = null;

    //构造方法
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    //启动
    public void start() throws IOException {
        System.out.println("服务器启动！！！");

        //利用线程池来解决多个客户端发起请求的问题
        ExecutorService pool = Executors.newCachedThreadPool();

        //利用主线程来 接收 客户端请求
        //利用其他线程来处理各个客户端请求（每个线程 对应 一个客户端）
        while(true){//——这个循环：循环接收不同客户端请求（与不同的客户端建立连接）
            //通过accept方法“接通电话”，然后才能进行通信
            Socket clientSocket = serverSocket.accept();

            //接同电话之后，由其他线程来处理具体的“通信”
            /*每次，针对每个客户端创建线程，为其提供响应服务
            Thread t = new Thread(()->{
                processConnection(clientSocket);
            });
            t.start();
            */

            //另一种：利用线程池
            pool.submit(new Runnable(){
                @Override
                public void run(){
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });

        }
    }


    //通过这个方法来处理一次连接，连接建立的过程中就会涉及到多次的请求响应交互。
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s : %d] 客户端上线！\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //循环的读取（这一个）客户端的请求并返回响应。
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){

            while(true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    //读取完毕，客户端断开连接，就会产生读取完毕。
                    System.out.println("[%s:%d]客户端下线！");
                    break;
                }

                //1.读取请求并分析，这里注意隐藏的约定。 next读的时候要读到空白符才会结束。
                //  因此就要求客户端发来的请求必须是带有空白符结尾。比如 \n 或者空格
                String request = scanner.next();

                //2.根据请求计算响应
                String response = process(request);
                //3.把相应返回给客户端
                //  通过这种方式可以写回，但是这种方式不方便给返回的响应中添加 \n
                //outputStream.write(response.getBytes(),0,response.getBytes().length);
                //  也可以给outputStream套上一层，完成更方便的写入。
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();//刷新/冲刷一下缓冲区

                System.out.printf("[%s : %d ] req:%s , resp:%s ",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);

            }
            //
        }finally {
            //每个客户端建立连接之后，都会分配一个Socket 对象，用于完成多次 请求-响应交互
            //不回收，文件描述符表可能会耗尽
            clientSocket.close();
        }
    }

    private String process(String request) {
        return request;
    }

    //
    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}
